package com.vmj.instructions;

import com.vmj.classfile.entity.MemberInfo;
import com.vmj.classfile.entity.attribute.CodeAttribute;
import com.vmj.common.BytesReader;
import com.vmj.heap.*;
import com.vmj.heap.VMClass;
import com.vmj.heap.ClassLoader;
import com.vmj.heap.VMObject;
import com.vmj.instructions.base.Instruction;
import com.vmj.instructions.base.NoOperandsInstruction;
import com.vmj.instructions.comparisions.*;
import com.vmj.instructions.constants.*;
import com.vmj.instructions.control.*;
import com.vmj.instructions.conversions.*;
import com.vmj.instructions.extended.IfNonNull;
import com.vmj.instructions.extended.IfNull;
import com.vmj.instructions.loads.*;
import com.vmj.instructions.math.*;
import com.vmj.instructions.references.*;
import com.vmj.instructions.reserved.InvokeNative;
import com.vmj.instructions.stack.*;
import com.vmj.instructions.stores.*;
import com.vmj.rtda.Frame;
import com.vmj.rtda.Thread;

/**
 * Created by yangshoujun on 2017/8/19 下午11:30.
 */
public class Interpreter {

    public static Instruction getInstruction(int opCode) {
        switch(opCode) {
            case 0x00:
                return new NoOperandsInstruction();
            case 0x01:
                return new AConstNull();
            case 0x02:
                return new IConstM1();
            case 0x03:
                return new IConst0();
            case 0x04:
                return new IConst1();
            case 0x05:
                return new IConst2();
            case 0x06:
                return new IConst3();
            case 0x07:
                return new IConst4();
            case 0x08:
                return new IConst5();
            case 0x09:
                return new LConst0();
            case 0x0a:
                return new LConst1();
            case 0x0b:
                return new FConst0();
            case 0x0c:
                return new FConst1();
            case 0x0d:
                return new FConst2();
            case 0x0e:
                return new DConst0();
            case 0x0f:
                return new DConst1();
            case 0x10:
                return new BiPush();
            case 0x11:
                return new SiPush();
            case 0x12:
                return new Ldc();
            case 0x13:
                return new Ldcw();
            case 0x14:
                return new Ldc2w();
            case 0x15:
                return new ILoad();
            case 0x16:
                return new LLoad();
//            case 0x17:
//                return &FLOAD{}
//            case 0x18:
//                return &DLOAD{}
            case 0x19:
                return new ALoad();
            case 0x1a:
                return new ILoad0();
            case 0x1b:
                return new ILoad1();
            case 0x1c:
                return new ILoad2();
            case 0x1d:
                return new ILoad3();
            case 0x1e:
                return new LLoad0();
            case 0x1f:
                return new LLoad1();
            case 0x20:
                return new LLoad2();
//            case 0x21:
//                return lload_3
            case 0x22:
                return new Fload0();
            case 0x23:
                return new Fload1();
            case 0x24:
                return new Fload2();
            case 0x25:
                return new Fload3();
//            case 0x26:
//                return dload_0
//            case 0x27:
//                return dload_1
//            case 0x28:
//                return dload_2
//            case 0x29:
//                return dload_3
            case 0x2a:
                return new ALoad0();
            case 0x2b:
                return new ALoad1();
            case 0x2c:
                return new ALoad2();
            case 0x2d:
                return new ALoad3();
             case 0x2e:
             	return new IALoad();
             case 0x2f:
             	return new LALoad();
             case 0x30:
             	return new FALoad();
             case 0x31:
             	return new DALoad();
             case 0x32:
             	return new AALoad();
             case 0x33:
             	return new BALoad();
             case 0x34:
             	return new CALoad();
             case 0x35:
             	return new SALoad();
            case 0x36:
                return new IStore();
            case 0x37:
                return new LStore();
//            case 0x38:
//                return &FSTORE{}
//            case 0x39:
//                return &DSTORE{}
            case 0x3a:
                return new AStore();
            case 0x3b:
                return new IStore0();
            case 0x3c:
                return new IStore1();
            case 0x3d:
                return new IStore2();
            case 0x3e:
                return new IStore3();
            case 0x3f:
                return new LStore0();
            case 0x40:
                return new LStore1();
            case 0x41:
                return new LStore2();
            case 0x42:
                return new LStore3();
//            case 0x43:
//                return fstore_0
//            case 0x44:
//                return fstore_1
//            case 0x45:
//                return fstore_2
//            case 0x46:
//                return fstore_3
//            case 0x47:
//                return dstore_0
//            case 0x48:
//                return dstore_1
//            case 0x49:
//                return dstore_2
//            case 0x4a:
//                return dstore_3
            case 0x4b:
                return new AStore0();
            case 0x4c:
                return new AStore1();
            case 0x4d:
                return new AStore2();
            case 0x4e:
                return new AStore3();
             case 0x4f:
             	return new IAStore();
             case 0x50:
             	return new LAStore();
             case 0x51:
             	return new FAStore();
             case 0x52:
             	return new DAStore();
             case 0x53:
             	return new AAStore();
             case 0x54:
             	return new BAStore();
             case 0x55:
             	return new CAStore();
             case 0x56:
             	return new SAStore();
            case 0x57:
                return new Pop();
            case 0x58:
                return new Pop2();
            case 0x59:
                return new Dup();
            case 0x5a:
                return new DupX1();
            case 0x5b:
                return new DupX2();
            case 0x5c:
                return new Dup2();
            case 0x5d:
                return new Dup2X1();
            case 0x5e:
                return new Dup2X2();
//            case 0x5f:
//                return swap
            case 0x60:
                return new IAdd();
            case 0x61:
                return new LAdd();
//            case 0x62:
//                return fadd
//            case 0x63:
//                return dadd
            case 0x64:
                return new ISub();
//            case 0x65:
//                return lsub
//            case 0x66:
//                return fsub
//            case 0x67:
//                return dsub
            case 0x68:
                return new Imul();
//            case 0x69:
//                return lmul
            case 0x6a:
                return new FMul();
//            case 0x6b:
//                return dmul
            case 0x6c:
                return new Idiv();
//            case 0x6d:
//                return ldiv
//            case 0x6e:
//                return fdiv
//            case 0x6f:
//                return ddiv
            case 0x70:
                return new IRem();
//            case 0x71:
//                return lrem
//            case 0x72:
//                return frem
//            case 0x73:
//                return drem
//            case 0x74:
//                return ineg
//            case 0x75:
//                return lneg
//            case 0x76:
//                return fneg
//            case 0x77:
//                return dneg
            case 0x78:
                return new Ishl();
            case 0x79:
                return new Lshl();
            case 0x7a:
                return new IShr();
//            case 0x7b:
//                return lshr
            case 0x7c:
                return new Iushr();
//            case 0x7d:
//                return lushr
            case 0x7e:
                return new IAnd();
            case 0x7f:
                return new LAnd();
            case 0x80:
                return new IOr();
//            case 0x81:
//                return lor
            case 0x82:
                return new IXor();
//            case 0x83:
//                return lxor
            case 0x84:
                return new IInc();
            case 0x85:
                return new I2l();
            case 0x86:
                return new I2f();
//            case 0x87:
//                return i2d
//            case 0x88:
//                return l2i
//            case 0x89:
//                return l2f
//            case 0x8a:
//                return l2d
            case 0x8b:
                return new F2i();
//            case 0x8c:
//                return f2l
//            case 0x8d:
//                return f2d
//            case 0x8e:
//                return d2i
//            case 0x8f:
//                return d2l
//            case 0x90:
//                return d2f
            case 0x91:
                return new I2b();
            case 0x92:
                return new I2c();
//            case 0x93:
//                return i2s
            case 0x94:
                return new LCmp();
            case 0x95:
                return new FCmpl();
            case 0x96:
                return new FCmpg();
//            case 0x97:
//                return dcmpl
//            case 0x98:
//                return dcmpg
            case 0x99:
                return new IfEq();
            case 0x9a:
                return new IfNe();
            case 0x9b:
                return new IFlt();
            case 0x9c:
                return new IfGe();
            case 0x9d:
                return new IfGt();
            case 0x9e:
                return new IfLe();
            case 0x9f:
                return new IfIcmpEq();
            case 0xa0:
                return new IfIcmpne();
            case 0xa1:
                return new IficpmLt();
            case 0xa2:
                return new IfCmpGe();
            case 0xa3:
                return new IfIcmpGt();
            case 0xa4:
                return new IfIcmple();
            case 0xa5:
                return new IfAcmpEq();
            case 0xa6:
                return new IfAcmpne();
            case 0xa7:
                return new Goto();
//            // case 0xa8:
//            // 	return &JSR{}
//            // case 0xa9:
//            // 	return &RET{}
//            case 0xaa:
//                return &TABLE_SWITCH{}
            case 0xab:
                return new LookupSwitch();
             case 0xac:
             	return new IReturn();
             case 0xad:
             	return new LReturn();
             case 0xae:
             	return new FReturn();
             case 0xaf:
             	return new DReturn();
             case 0xb0:
             	return new AReturn();
             case 0xb1:
             	return new Return();
            case 0xb2:
                return new GetStatic();
            case 0xb3:
                return new PutStatic();
            case 0xb4:
                return new GetField();
            case 0xb5:
                return new PutField();
            case 0xb6:
                return new InvokeVirtual();
            case 0xb7:
                return new InvokeSpecial();
             case 0xb8:
             	return new InvokeStatic();
             case 0xb9:
             	return new InvokeInterface();
//            // case 0xba:
//            // 	return &INVOKE_DYNAMIC{}
            case 0xbb:
                return new New();
             case 0xbc:
             	return new NewArray();
             case 0xbd:
             	return new ANewArray();
             case 0xbe:
             	return new ArrayLength();
             case 0xbf:
             	return new Athrow();
            case 0xc0:
                return new CheckCast();
            case 0xc1:
                return new InstanceOf();
             case 0xc2:
             	return new MonitorEnter();
             case 0xc3:
             	return new MonitorExit();
//            case 0xc4:
//                return &WIDE{}
             case 0xc5:
             	return new MultiANewArray();
            case 0xc6:
                return new IfNull();
            case 0xc7:
                return new IfNonNull();
//            case 0xc8:
//                return &GOTO_W{}
//            // case 0xc9:
//            // 	return &JSR_W{}
//            // case 0xca: breakpoint
             case 0xfe:
                 return new InvokeNative();
//            // case 0xff: impdep2
            default:
                throw new RuntimeException("unsupported opcode: " + opCode);
        }
    }

    public void interpret(MemberInfo methodInfo) {

        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        int maxLocals = codeAttribute.getMaxLocals();
        int maxStack = codeAttribute.getMaxStack();
        byte[] byteCode = codeAttribute.getCode();

        Thread thread = new Thread();
        Frame frame = new Frame(maxLocals, maxStack);
        frame.setThread(thread);
        thread.pushFrame(frame);
        thread.loop(byteCode);
    }

    public void interpret(Method method) {

        Thread thread = new Thread();
        Frame frame = new Frame(thread, method);
        thread.pushFrame(frame);
        thread.loop(method.getCode());
    }

    public void loop(Method method, String[] args) {

        Thread thread = new Thread();
        Frame frame = new Frame(thread, method);
        thread.pushFrame(frame);
        VMObject argsObj = createArgsArray(method.getClazz().getClassLoader(), args);
        frame.getLocalVars().setRef(0, argsObj);
        loop(thread);
    }

    public void loop(Thread thread) {
        // 字节码读取类
        BytesReader reader = new BytesReader();
        while(true) {
            // 获取当前帧
            Frame frame = thread.getCurrentFrame();
            // 获取PC
            int pc = frame.getNextPc();
            thread.setPc(pc);
            reader.reset(frame.getMethod().getCode(), pc);
            // 读取操作码
            int opCode = reader.readByte();
            // 转换为无符号byte类型
            opCode &= 0xFF;
            // 获取指令对象
            Instruction instruction = Interpreter.getInstruction(opCode);
            // 读取操作数
            instruction.fetchOperands(reader);
            // 设置PC
            frame.setNextPc(reader.getOffset());
            // 执行指令
            instruction.execute(frame);
            // 栈为空时结束
            if(thread.isStackEmpty()) {
                break;
            }
        }
    }

    private VMObject createArgsArray(ClassLoader classLoader, String[] args) {
        VMClass stringClass = classLoader.loadClass("java/lang/String");
        VMObject argsObj = VMObject.newArray(stringClass.getArrayClass(), args.length);
        VMObject[] refs = (VMObject[]) argsObj.getData();
        for(int i = 0;i < args.length;i++) {
            refs[i] = StringPool.getStringObject(classLoader, args[i]);
        }

        return argsObj;
    }
}
